using System;
using System.Runtime.InteropServices;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.Runtime.Serialization;
using System.Diagnostics;

namespace AltSerialize
{
    public static class ReflectionHelper
    {
        private static Dictionary<Type, bool> HasCompiledSerializerAttribute = new Dictionary<Type, bool>();
        private static Dictionary<Type, bool> ImplementsIAltSerializable = new Dictionary<Type, bool>();        
        private static Dictionary<Type, FieldInfo[]> FieldInfoLookup = new Dictionary<Type, FieldInfo[]>();
        public static Dictionary<Type, int> _hashTypeInt = new Dictionary<Type, int>();
        public static Dictionary<int, Type> _hashIntType = new Dictionary<int, Type>();

#if USE_ADDITIONAL_TYPES
        public static List<Type> AdditionalTypes = new List<Type>();
#endif

        public static bool CheckHasCompiledSerializerAttribute(Type type)
        {
            bool hasCompiledSerializerAttribute = false;
            if (!HasCompiledSerializerAttribute.TryGetValue(type, out hasCompiledSerializerAttribute))
            {
                try
                {
                    hasCompiledSerializerAttribute = (type.GetCustomAttributes(typeof(CompiledSerializerAttribute), true).Length != 0);
                    HasCompiledSerializerAttribute.Add(type, hasCompiledSerializerAttribute);
                }
                catch { }
            }

            return hasCompiledSerializerAttribute;
        }

        public static void AddType(this Type objectType, int hashId)
        {
            _hashIntType.Add(hashId, objectType);
            _hashTypeInt.Add(objectType, hashId);
        }

        // Adds default types to the serializer.
        public static void AddTypes()
        {
            // Primitive integer types
            AddType(typeof(int), 0);
            AddType(typeof(uint), 1);
            AddType(typeof(short), 2);
            AddType(typeof(ushort), 3);
            AddType(typeof(byte), 4);
            AddType(typeof(sbyte), 5);
            AddType(typeof(long), 6);
            AddType(typeof(ulong), 7);

            // Primitive decimal types
            AddType(typeof(float), 8);
            AddType(typeof(double), 9);
            AddType(typeof(decimal), 10);

            // Nullable integer types
            AddType(typeof(int?), 20);
            AddType(typeof(uint?), 21);
            AddType(typeof(short?), 22);
            AddType(typeof(ushort?), 23);
            AddType(typeof(byte?), 24);
            AddType(typeof(sbyte?), 25);
            AddType(typeof(long?), 26);
            AddType(typeof(ulong?), 27);

            // Nullable decimal types
            AddType(typeof(float?), 28);
            AddType(typeof(double?), 29);
            AddType(typeof(decimal?), 30);

            // Char
            AddType(typeof(char), 31);
            AddType(typeof(char?), 32);

            // Bool
            AddType(typeof(bool), 33);
            AddType(typeof(bool?), 34);

            // Integral array types
            AddType(typeof(int[]), 40);
            AddType(typeof(uint[]), 41);
            AddType(typeof(short[]), 42);
            AddType(typeof(ushort[]), 43);
            AddType(typeof(byte[]), 44);
            AddType(typeof(sbyte[]), 45);
            AddType(typeof(long[]), 46);
            AddType(typeof(ulong[]), 47);

            // Decimal array types
            AddType(typeof(float[]), 48);
            AddType(typeof(double[]), 49);
            AddType(typeof(decimal[]), 50);

            // Char array, bool
            AddType(typeof(char[]), 51);
            AddType(typeof(bool[]), 52);

            // Other system value types
            AddType(typeof(TimeSpan), 100);
            AddType(typeof(DateTime), 101);
            AddType(typeof(Guid), 102);
            AddType(typeof(TimeSpan?), 103);
            AddType(typeof(DateTime?), 104);
            AddType(typeof(Guid?), 105);
            AddType(typeof(string), 106);

            AddType(typeof(DateTime[]), 110);
            AddType(typeof(TimeSpan[]), 111);
            AddType(typeof(Guid[]), 112);
            AddType(typeof(string[]), 113);

            AddType(typeof(object), 250);
            AddType(typeof(object[]), 251);
            AddType(typeof(Type), 252);
            AddType(typeof(Type[]), 253);
        }

        public static bool CheckImplementsIAltSerializable(this Type type)
        {
            bool isIAltSerializable;

            if (!ImplementsIAltSerializable.TryGetValue(type, out isIAltSerializable))
            {
                try
                {
                    isIAltSerializable = type.GetInterface(typeof(IAltSerializable).Name, true) != null;
                    ImplementsIAltSerializable.Add(type, isIAltSerializable);
                }
                catch { }
            }

            return isIAltSerializable;
        }
        public static void GetAllFieldsOfType(Type type, List<FieldInfo> fieldList)
        {
            if (type == typeof(object))
                return;

            FieldInfo[] fields;

            if (!FieldInfoLookup.TryGetValue(type, out fields))
            {
                if (type == null || type == typeof(ValueType))
                {
                    return;
                }

                fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public
#if SERIALIZE_NON_PUBLIC
                    | BindingFlags.NonPublic
#endif
                    );

                for (int i = 0; i < fields.Length; i++)
                {
                    var field = fields[i];

                    if (!field.IsFieldSerializable())
                        continue;

                    if (field.IsNotSerialized)
                        continue;
                }
                FieldInfoLookup.Add(type, fields);
            }

            for (int i = 0; i < fields.Length; i++)
            {
                var field = fields[i];

                if (fieldList.FirstOrDefault((fi) => fi.Name == field.Name) == null)
                {
                    fieldList.Add(fields[i]);
                }
            }

            GetAllFieldsOfType(type.BaseType, fieldList);
        }
    }
}
